#include <stdio.h>
#include <string.h>
#include <omnetpp.h>
#include <sortNumberMsg_m.h>
#include <highestNumberMsg_m.h>
#include <conditionMsg_m.h>
#include <constraintMsg_m.h>
#include <colorMsg_m.h>

#define MAXINT 1000
#define MAXCOLORS 50

#define COLORNAMES 10
#define MAXLINKCOLORS 5
	char colorname[COLORNAMES][20] = { "red", "green", "blue", "yellow", "cyan", "white", "purple", "gold", "orange" , "gray"};

#undef COLORNODES
#define COLORLINKS

class node2: public cSimpleModule
{
	public:
	enum state { A = 0, B, C, D, E, F }; // needs to be defined before function declarations
#define NUMSTATES 6

	protected:
	virtual void initialize();
	virtual void handleMessage(cMessage *msg);
	virtual void updateDisplay();

	virtual int getLinkColor(int c1, int c2);

	virtual void sendToNeighbors(cMessage *msg);
	virtual int checkColors();
	virtual int checkReceived(enum state state);
	virtual void resetReceived(enum state state);
	virtual void resetNumbers();

	virtual void stateTransition();

	virtual void recvSortNumber(cMessage *msg);
	virtual void recvHighestNumber(cMessage *msg);
	virtual void recvCondition(cMessage *msg);
	virtual void recvConstraints(cMessage *msg);
	virtual void recvColor(cMessage *msg);

	virtual void stateAinit();

	virtual void stateBinit();

	virtual void stateCinit();

	virtual void stateDinit();

	//virtual void stateEinit(); // currently not used
	virtual void stateEcolorSelf();

	public:
	enum status { inactive = 0, active = 1 };
	enum condition { winner = 1, neighbor, other };

	int degree;
	int link;
	int winnerlink;
	int color;
	int sortNumber;
	int highestNumber;
	enum state state;
	enum status status;
	enum condition condition;

	int *received[NUMSTATES];
	enum status *neighborStatus;
	int *neighborSortNumber;
	int *neighborHighestNumber;
	int *neighborColor;
	int **neighborConstraints;

	char buf[30];
};

Define_Module(node2);

void node2::initialize()
{
	state = A;
	status = active;
	color = -1;

	degree = gateSize("gate");

	for (int i = 0; i < NUMSTATES; i++) {
		received[i] = new int [degree];
		resetReceived((enum state) i);
	}
	neighborStatus = new enum status [degree];
	neighborSortNumber = new int [degree];
	neighborHighestNumber = new int [degree];
	neighborColor = new int [degree];
	neighborConstraints = new int* [degree];

	for (int i = 0; i < degree; i++) {
		neighborStatus[i] = active;
		neighborColor[i] = -1;
		neighborConstraints[i] = NULL;
	}

	resetNumbers();

	stateAinit();

	if (ev.isGUI()) { updateDisplay(); }
}

void node2::handleMessage(cMessage *msg)
{
	link = (msg->getArrivalGate())->getIndex();

	EV << "node " << getIndex() << " received " << msg->getName() << " message from neighbor " << link << "\n";

	if (!strcmp(msg->getName(), "sortNumber")) { recvSortNumber(msg); }
	else if (!strcmp(msg->getName(), "highestNumber")) { recvHighestNumber(msg); }
	else if (!strcmp(msg->getName(), "condition")) { recvCondition(msg); }
	else if (!strcmp(msg->getName(), "constraints")) { recvConstraints(msg); }
	else if (!strcmp(msg->getName(), "color")) { recvColor(msg); }

	stateTransition();

	if (ev.isGUI()) { updateDisplay(); }
}

void node2::updateDisplay()
{
	char buffer[40];
	cDisplayString &dispStr = getDisplayString();

	sprintf(buffer, "status: %d\nstate: %d\ncolor: %d", status, state, color);
	dispStr.setTagArg("t",0,buffer);

#ifdef COLORLINKS
	int linkcolor;

	if (state == F) {
		EV << "node " << getIndex() << " coloring links\n";
		for (int i = 0; i < degree; i++){
			linkcolor = getLinkColor(color, neighborColor[i]);
			EV << "node " << getIndex() << " link to neighbor "<< i << " color " << linkcolor << "\n";
			gate("gate$o", i)->getDisplayString().setTagArg("ls", 0, colorname[linkcolor]);
		}
	}
#endif

#ifdef COLORNODES
	if (color != -1) {
		dispStr.setTagArg("i", 1, colorname[color]);
	}
#endif
}

int node2::getLinkColor(int c1, int c2)
{
	int hi, lo;
	int col = 0;

	if (c1 > c2) {
		hi = c1;
		lo = c2;
	}
	else if (c2 > c1) {
		hi = c2;
		lo = c1;
	}

	for (int i = 0; i < MAXLINKCOLORS-1; i++)
	{
		for (int j = (i + 1); j < MAXLINKCOLORS; j++)
		{
			if (i == lo && j == hi) {
				return col;
			}
			col++;
		}
		col++;
	}

	return -1;
}

void node2::sendToNeighbors(cMessage *msg)
{
	for (int i = 0; i < degree; i++) {
		if (neighborStatus[i] == active) {
			cMessage *copy = msg->dup();
			send(copy, "gate$o", i);
		}
	}
}

int node2::checkColors()
{
	if (color == -1) { return 0; }
	for (int i = 0; i < degree; i++) {
		if (neighborColor[i] == -1) { return 0; }
	}
	return 1;
}

int node2::checkReceived(enum state state)
{
	for (int i = 0; i < degree; i++) {
		if (neighborStatus[i] == active) {
			if (received[state][i] != 1) { return 0; }
		}
	}
	return 1;
}

void node2::resetReceived(enum state state)
{
	for (int i = 0; i < degree; i++) {
		received[state][i] = 0;
	}
}

void node2::resetNumbers()
{
	for (int i = 0; i < degree; i++) {
		neighborSortNumber[i] = -1;
		neighborHighestNumber[i] = -1;
	}
}

void node2::stateTransition()
{
	switch(state) {
		case A:
			if (checkReceived(state)) {
				if (status == inactive) {
					EV << "node " << getIndex() << " state F\n";
					state = F;
					if (ev.isGUI()) { updateDisplay(); }
				}
				else {
					EV << "node " << getIndex() << " state B\n";
					state = B;
					resetReceived(A);
					stateBinit();
				}
			}
			break;

		case B:
			if (checkReceived(state)) {
				for (int i = 0; i < degree; i++) {
					if (neighborHighestNumber[i] > highestNumber) { highestNumber = neighborHighestNumber[i]; }
				}
				if (sortNumber == highestNumber) {
					condition = winner;
				}
				EV << "node " << getIndex() << " state C\n";
				state = C;
				resetReceived(B);
				resetNumbers();
				stateCinit();
			}
			break;

		case C:
			if (checkReceived(state)) {
				resetReceived(C);
				switch(condition) {
					case winner:
							EV << "node " << getIndex() << " winner\n";
							bubble("winner");
							EV << "node " << getIndex() << " state C\n";
							state = E;
							//stateEinit();
						break;
					case neighbor:
							EV << "node " << getIndex() << " winner neighbor\n";
							bubble("neighbor");
							EV << "node " << getIndex() << " state D\n";
							state = D;
							stateDinit();
						break;
					case other:
							EV << "node " << getIndex() << " other\n";
							bubble("other");
							EV << "node " << getIndex() << " state A\n";
							state = A;
							stateAinit();
						break;
				}
			}
			break;

		case D:
			if (link == winnerlink) {
				if(checkColors()) {
						status = inactive;
						EV << "node " << getIndex() << " status inactive\n";
						sprintf(buf, "inactive");
				}
				EV << "node " << getIndex() << " state A\n";
				state = A;
				stateAinit();
			}
			break;

		case E:
			if (checkReceived(E)) {
				stateEcolorSelf();
				if(checkColors()) {
						status = inactive;
						EV << "node " << getIndex() << " status inactive\n";
						sprintf(buf, "inactive");
				}
				EV << "node " << getIndex() << " state A\n";
				state = A;
				resetReceived(E);
				stateAinit();
			}
			break;

		case F:
			break;

	}
}

void node2::recvSortNumber(cMessage *msg)
{
	sortNumberMsg *snmsg;
	snmsg = check_and_cast<sortNumberMsg *>(msg);

	neighborStatus[link] = (enum status) snmsg->getStatus();
	neighborSortNumber[link] = snmsg->getNumber();
	received[A][link] = 1;
}

void node2::recvHighestNumber(cMessage *msg)
{
	highestNumberMsg *hnmsg;
	hnmsg = check_and_cast<highestNumberMsg *>(msg);

	neighborHighestNumber[link] = hnmsg->getNumber();
	received[B][link] = 1;
}

void node2::recvCondition(cMessage *msg)
{
	conditionMsg *cmsg;
	cmsg = check_and_cast<conditionMsg *>(msg);

	if (cmsg->getCondition() == winner && neighborStatus[link] == active) {
		condition = neighbor;
		winnerlink = link;
	}
	received[C][link] = 1;
}

void node2::recvConstraints(cMessage *msg)
{
	constraintMsg *cnmsg;
	cnmsg = check_and_cast<constraintMsg *>(msg);

	int size = cnmsg->getConstraintsArraySize();
	neighborConstraints[link] = new int [size + 1];
	neighborConstraints[link][0] = size;
	for (int i = 1; i <= size; i++) {
		neighborConstraints[link][i] = cnmsg->getConstraints(i - 1);
	}
	received[E][link] = 1;
}

void node2::recvColor(cMessage *msg)
{
	colorMsg *clmsg;
	clmsg = check_and_cast<colorMsg *>(msg);

	neighborColor[link] = clmsg->getColor();
}

void node2::stateAinit()
{
	winnerlink = -1;
	condition = other;

	sortNumberMsg *msg = new sortNumberMsg("sortNumber");
	sortNumber = intuniform(0, MAXINT);
	if (color != -1) sortNumber = -1;
	EV << "node " << getIndex() << " sortNumber " << sortNumber << "\n";
	msg->setNumber(sortNumber);
	msg->setStatus(status);
	sendToNeighbors(msg);
}

void node2::stateBinit()
{
	highestNumber = sortNumber;
	for (int i = 0; i < degree; i++) {
		if (neighborSortNumber[i] > highestNumber) { highestNumber = neighborSortNumber[i]; }
	}
	EV << "node " << getIndex() << " highestNumber " << highestNumber << "\n";
	highestNumberMsg *msg = new highestNumberMsg("highestNumber");
	msg->setNumber(highestNumber);
	sendToNeighbors(msg);
}

void node2::stateCinit()
{
	conditionMsg *msg = new conditionMsg("condition");
	msg->setCondition(condition);
	sendToNeighbors(msg);
}

void node2::stateDinit()
{
	if (winnerlink != -1) {
		constraintMsg *msg = new constraintMsg("constraints");
		msg->setConstraintsArraySize(degree+1);
		for (int i = 0; i < degree; i++) {
			msg->setConstraints(i, neighborColor[i]);
		}
		msg->setConstraints(degree, color);
		send(msg, "gate$o", winnerlink);
	}
}

/* //currently not used
void node2::stateEinit()
{
}*/

void node2::stateEcolorSelf()
{
	int used = 0;
	if (color == -1) {
		for (int c = 0; c < MAXCOLORS; c++) {
			used = 0;
			for (int i = 0; i < degree; i++){
				for (int j = 1; j <= neighborConstraints[i][0]; j++) {
					if (neighborConstraints[i][j] == c) {
						used = 1;
						break;
					}
				}
				if (used == 1) { break; }
			}
			if (used == 0) {
				color = c;
				break;
			}
		}
	}
	colorMsg *msg = new colorMsg("color");
	EV << "node " << getIndex() << " color " << color << "\n";
	sprintf(buf, "color %d", color);
	bubble(buf);
	msg->setColor(color);
	sendToNeighbors(msg);
}
